/**
 *  Sprite.java
 *
 *  Sprites are simple objects present in a game.
 *  Eg of sprites in a game: brick,bullet,cows,dragon,warpzones,ghosts etc.
 *  A sprite may have artificial intelligence,it may be visible/invisible,it may
 *  be controlled by human player or self controlled.
 *
 *	NOTE :  Sprite class was designed such that -
 *			Any object can easily spawn/access/meddle/modify/kill any alive Sprite object.
 *			Here any object literally means any object including the Sprite objects too.
 *
 *
 * @author Somanath
 * @version 1.1	 (last updated 19/09/03)
 *
 * Class Dependencies... CImage (since CImage[] images is a member of Sprite)
 *                       Direction interface.  (canMove(int direction))
 *                       ThreadState ( int threadState member requires it)
 */

package gamepack;

import java.awt.Rectangle;
import java.awt.Graphics;
import java.util.Vector;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collections;
import java.awt.Rectangle;
import java.awt.Polygon;

import java.util.*;
import java.awt.Color;
import java.util.Enumeration;

// Required to Embed Compiler
import org.codehaus.janino.*;
import org.codehaus.janino.util.resource.*;
import java.io.*;

/**  All subclasses of Sprite should declare this static member.
 *   public static Vector family = createFamily();
 *
 *   This is required because the family should get created very
 *   early (even before the first instance of the Sprite subclass
 *   is created). The only mechanism is using static variable.
 *   If not this could have been hidden in the startSprite() method.
 * */

// Todo: Use implements Comparable<Sprite>
public abstract class Sprite implements Runnable,Direction, Collidable, SpriteState,Comparable<Sprite> {

	/** Sprite position */
	public int x,y;

	/** Signifies the state of the sprite. It may have the following
	    predefined values: <br> NORMAL=0,DEAD=-1. 	<br>

		if state < 0 then other sprites can't see it, but it is visible on
        screen. if visible == false then sprite does not appear on screen. */

	public int state = DEAD;

    /** This is set to true everytime setState( blahState) is called.
     It indicates that the code for the new state is going to be invoked
     the first time. Eg.

     say some code does - setState( flying );
     which in turns invokes a method :
    void flying() {
         if ( firstTime ) {
            spread_wings();
         }
         else {
            flap_wings();  // repeatedly called during 'flying' state until state change.
            if ( tired )
                setState( gliding );
         }
    }     */
    public boolean firstTime = true;

    // If not overriden, the health of a sprite is set to 100
    public int health = 100;

    // Returns true if hurt.
    // This is overridden in derived class and enables
    // a sprite to decide how much it actually gets hurt.
    // Eg. if the sprite has an armor it will override this method
    // to actually get less hurt.
    public boolean hurt(int damage) {
           health -= damage;
           if( health <= 0 ) setState( DYING );
           return true;
    }

    // Reference to resources for the Sprite. This may contain image/sound and other data.
    public Resource res;

    /** Type of Sprite. Also determines how a sprite looks.
     *  This attribute give the flexibility of having skins for the sprite. */
    public int type;
	/**
	The current Frame of the sprite.(Note: 0 <= curFrame <= images.length-1) */
	public int curFrame;

	/** Visibility flag of the sprite. */
	public boolean visible;

    public String toString() {  // TODO: make this error free
           String s;
           Rectangle r = res.img[type][curFrame].getBounds(); //rect;
           String name = getShortName();
           s = name + "(" + x + "," + y + ")" + " rect(" + r.x + "," + r.y + "," + r.width + "," + r.height + ")";
		   //s = name + "attributes[]=" + StringArgs( 
		   
           return s;
    }

    // Typical this is overridden in derived class.
    public boolean canTurn(int newDirection) {
           return true;
    }

	//-----------------------------------------------------------------
	// These attributes are optional. All sprites might not use them.
	// ----------------------------------------------------------------

	/** Direction the sprite is facing. */
	public int dir;

	/** Velocity of the sprite */
	public int vx,vy;

	/* Array of different families of other kinds of Sprites which
	   will behave like blocks only for this object.
	   Check if it is possible to turn this to String[] instead.
       */
	public Vector[] blockers;
	public Vector[] colliders;

	/** Sprite's z order. If 2 sprites overlap, then the sprite having
	    lower value of z will be drawn first.  */
	private int z;
	private boolean started = false;
	
	/**  The parent Sprite who spawn me. Optional field. Gives flexiblity in sprite AI, when any sprites wants to find out more
		information about other sprite in its environment.
		Eg. You may not want to kill sprites who have the same parent as you.
		OR  You may not want to kill sprites who have the same parent as your kind.
	*/
	public Sprite parent = null;


		protected CImage getCImage() { // Only for defensive check instead of directly using res.img[type][curFrame]
			  if( res == null ) {
				throw new GamePackException(getShortName()+".res == null Check if it is initialized in " + getShortName() + " Constructor.");
			  }
  			  CImage[] cimg1 = res.img[type];  //[curFrame];
			  if( cimg1 == null ) {
				throw new GamePackException("res.img[type="+type+"][curFrame] == null Check if curFrame is valid");
			  }
			  CImage cimg2 = cimg1[curFrame];
			  if( cimg2 == null ) {
			   throw new GamePackException("res.img[type="+type+"][curFrame="+curFrame+"] == null Check if type & curFrame combination is valid");
			  }	
			  return cimg2;
		}
		
       public Rectangle getBounds() { // get the collision rectangle defined wrt image's center then translate it to sprite's x and y
		  CImage cimg = getCImage();
		  Rectangle r = new Rectangle( cimg.getBounds() );			  
		  r.translate( x,y );
		  return r;
       }	   
	   
       public boolean hasSubShape() { 
			return getCImage().hasSubShape();
	   }
	   
	   public void translate(int dx,int dy) {
			x += dx;
			y += dy;
	   }
       
	   public Polygon getSubShape() { 
			Polygon ppp = getCImage().getSubShape();
			Polygon p = new Polygon( ppp.xpoints, ppp.ypoints, ppp.npoints);
			p.translate( x,y );
			return p;
	   }

	// ----------------------------------------------------------------
	// Constructors
	// ----------------------------------------------------------------

	   protected static SpriteArgs args = new SpriteArgs();
		public static SpriteArgs getArgs() {
			return args;
		}
		public static void setArgs(int x,int y,int type,int curFrame,
	              String[] attributes_,String[] blockers,String[] colliders,
	              int dir,boolean visible,int vx,int vy, 
				  Map<String,String> strMap, Map<String,String[]> strArrMap, Sprite sprObj ) {
			args.set(x,y,type,curFrame,attributes_,blockers,colliders,dir,visible,vx,vy,strMap,strArrMap,sprObj);
		}
		
		public static SpriteArgs setArgs(String spawnString,Sprite sprObj) {
			args.set(spawnString,sprObj);
			return args;
		}
		// Think of extra/other simpler interface like the printf for spawnString.
		/*public static Sprite spawn(String spawnString,Sprite sprObj,Object[] args) {
			return Sprite.spawn( String.format( spawnString, args ), sprObj );
		}*/
		
		public static Sprite spawn(String spawnString,Sprite sprObj) {
			int index = spawnString.indexOf('#');
			String part1 = spawnString.substring( 0 , index).trim();
			String part2 = spawnString.substring(index+1, spawnString.length() );
			if( part1.charAt(0) == '@' ) { // Not strictly required. (Required only if we need to differentiate dynamic spawn.]
				part1 = part1.substring( 1, part1.length() );
			} 
			return spawnClass(part1,part2,sprObj);
		}
		
		public static Sprite spawnClass(String className, String spawnString) {
			return spawnClass(className,spawnString,null);
		}
		
		public static Sprite spawnClass(String className, String spawnString,Sprite sprObj) {
			args.set(spawnString,sprObj);
			return spawnClass( className, args );
		}

		// <Embed Compiler Begin> -----------------------------------------------
		// Note - There is a bug in codehaus compiler will be fixed in its next version 6.2.0
		// The bug causes complier to crash while loading class with switch case construct.
		
		
		
		public static MultiResourceFinder getMultiResourceFinder() {
			Collection resourceFinders = new Vector();
			resourceFinders.add( new DirectoryResourceFinder(new File(".")) );
			resourceFinders.add( new CustomResourceFinder() );
			return new MultiResourceFinder( resourceFinders );
		}
		
		public static MultiResourceFinder msf = getMultiResourceFinder(); 
		
		
		/*public static ClassLoader dynamicClassLoader = new JavaSourceClassLoader( // Original simple one
			ClassLoader.getSystemClassLoader()	// parentClassLoader    
			//new CustomResourceFinder() 	// resourcefinder
			,(java.io.File[]) null   			// optionalSourcePath
			,(String) null    					// optionalCharacterEncoding
			//	(java.io.File) null 			// Required for CachingJavaSourceClassLoader
			);
  */
		public static ClassLoader dynamicClassLoader = new JavaSourceClassLoader( // Also see CachingJavaSourceClassLoader
			ClassLoader.getSystemClassLoader()				// parentClassLoader    
			, msf 	// resourcefinder
			//,(java.io.File[]) null   			// optionalSourcePath
			,(String) null    					// optionalCharacterEncoding
			//	(java.io.File) null 			// Required for CachingJavaSourceClassLoader
			); 
			
		public static Class getSpriteClass(String className) throws ClassNotFoundException {
			return dynamicClassLoader.loadClass(className);
			// return ClassLoader.getSystemClassLoader(); // The default ClassLoader 
		}
		// </Embed Compiler End> ----------------------------------------------------
		
		public static Sprite getDummyInstance(String spawnString) {
			Sprite s = spawn( spawnString, null );
			s.setState(DEAD);
			s.visible = false; 
			return s;
		}

	   public static Sprite spawnClass(String className, SpriteArgs spr_args) {
	          Sprite spr;
            try {
				args = spr_args; // should be self assignment when called by Sprite.spawn(className,spawnString,sprObj)
				Class c = getSpriteClass(className); 
				if( className == null ) {
					throw new RuntimeException(" spawnClass(null) invoked in Sprite.spawnClass() " );
				}
				if( c == null ) {
					throw new RuntimeException(" Sprite.getSpriteClass("+className+") returned null in spawnClass() " );
				}
				//System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@ about to spawn a Sprite "+className+ " @@@@@@@@@@@@@@@@@@@@@@@@@@@@");
				spr = (Sprite) c.newInstance();
				//System.out.println("--------------------- Hoorey success in spawn of Sprite "+className+ " ------------------------");
	        }
			   catch( RuntimeException e ) {					
					throw new GamePackException( "RuntimeException in Sprite.spawnClass() " + e.getClass().getName() + " : " + e.getMessage() );
			   }
               catch( ExceptionInInitializerError e ) {
                      String s = className + ":ExceptionInInitializerError: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( LinkageError e ) {
                      String s = className + ":LinkageError: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( ClassNotFoundException e ) {
                      String s = className + ":ClassNotFoundException: " + e.getCause();
                      throw new GamePackException(s);
               }
               catch( Throwable e ) {
                      String s = className + ":Throwable: " + e.getClass().getName() + " e.getCause() " + e.getCause() ;
                      throw new GamePackException( s );
               }
              return spr;
	          // 1. fillup args using formatString
	          // 2. call defaultCtr of className and return;
	   }

	/** Default constructor. 	*/
	public Sprite() {	//this(args.x,args.y,  args.type,args.curFrame,null,  null,null,null,  0,true,0,0);	}
		this.x = args.x;
		this.y = args.y;
	    this.z = args.z;
		this.type = args.type;
		this.curFrame = args.curFrame;
		//if( spriteResource != null ) {
		 //   this.res = res; //ResourceLoader.getSpriteResource( spriteResource );
		//}
		this.visible = args.visible;
		this.dir = args.dir;
		this.vx = args.vx;
		this.vy = args.vy;
		
		this.blockers = Environment.getAttributes( args.blockers ); //args.blockers;		
		this.colliders = Environment.getAttributes( args.colliders ); //args.colliders;
		this.parent = args.spr;
		state = DEAD; 
		
		setAttribute(args.attributes);	
				
	}

	/* Longest Constructor */
	public Sprite(int x,int y,int type,int curFrame,Resource res,
	              String[] attributes_,Vector[] blockers,Vector[] colliders,
	              int dir,boolean visible,int vx,int vy) {
		this.x = x;
		this.y = y;
	    this.z = 0;
		this.type = type;
		this.curFrame = curFrame;
		//if( spriteResource != null ) {
		    this.res = res; //ResourceLoader.getSpriteResource( spriteResource );
		//}
		this.visible = visible;
		this.dir = dir;
		this.vx = vx;
		this.vy = vy;

		this.blockers = blockers;
		this.colliders = colliders;

		state = DEAD;
		setAttribute(attributes_);
	}

	/* smallest constructor */
	public Sprite(int x,int y,int type,int curFrame,Resource res) {
		this(x,y,type,curFrame,res,
			null,null,null,
			0,true,0,0);
	}

	/* medium constructor */
	public Sprite(int x,int y,int type,int curFrame,Resource res,
	              String[] attributes_,Vector[] blockers,Vector[] colliders) {
		this(x,y,type,curFrame,res,
			attributes_,blockers,colliders,
			0,true,0,0);
	}
	// ----------------------------------------------------------------

    // Returns true if set if successfull. Sprites can use notify() and
    // getProperty() to modify/get info about other sprites without knowing
    // its type. This removes compile time dependency amongst different sprites.

    public boolean notify(int propertyName,String ... propertyValue) {
           return false;
    }

    
    public boolean notify(int propertyName,int ... propertyValue) {
           return false;
    }

    /* this should be cleaned up hence commenting.
	public int[] getProperty(String propertyName) {
           return null;
    }
	public String[] getProperty(String propertyName) {
           return null;
    }
	*/

	// ----------------------------------------------------------------

	/** This method returns true if the Sprite is in a valid
	    or usable state, else it returns false. A sprite is said to
	    be invalid if its "state" field's value is less than zero, else
	    it is a valid sprite. */
	public boolean valid() { return state<0 ? false : true; }

	public void handleCollision(Sprite s) { if( s.valid() ) s.setState(DEAD); }

    public void handleCollision(Sprite s1,Sprite s2) { System.out.println("Warning.. base class handle prey invoked"); }

	/** returns true if the param "attribute" is set for the sprite */
	public boolean getAttribute(String attribute_) {
		return ( attributes.contains( attribute_ ) );
	}
	public Set<String> getAttribute() {	return attributes;	}

	/** sets the z order for the sprite. Sprites with lower z order are displayed
		behind those with higher z order. If z order values for 2 sprites are same
		then the order for displaying them is undefined.  <br>
		Note: z can take both positive and negative values. By default, the value
		for z is zero. */
	public void setZOrder(int z) {
	    if( !started ) { this.z = z; return; }
		Environment.allSprites.removeElement(this);
		int pos = Collections.binarySearch(  Environment.allSprites,this);
		if( pos < 0 ) pos = - (pos+1);
		Environment.allSprites.insertElementAt(this,pos);
		this.z = z;
	}

	public int getZOrder() {	return z;	}

	/** Compares two sprites as per their z order. <br>
		@ returns positive value if calling object's z order is greater
		than the passed sprite object.
	*/
	public int compareTo(Sprite s) { return this.z - s.z; };

    public void unsetAttribute(String attribute_) {
        attributes.remove( attribute_ );
        if ( Environment.familyOfAttributes.containsKey( attribute_ ) ) {
           // nothin will happen if this sprite wasn't present in the container
           Environment.familyOfAttributes.get( attribute_).remove( this );
        }
    }
    // Removes all attributres
    public void unsetAttribute() {
        for ( String s : attributes ) {
           if ( Environment.familyOfAttributes.containsKey( s ) ) {
              // nothin will happen if this sprite wasn't present in the container
              Environment.familyOfAttributes.get( s ).remove( this );
           }
        }
        attributes.clear();
    }

	
	public void setAttribute(String ... attributes_) {
	       if ( attributes_ == null ) return;
	       for( int i=0 ; i < attributes_.length ; i++) {
	            setAttribute( attributes_[i] );
	        }
	}
	
	// -- Overloaded setAttribute in case ... operator is not supported by client code.
	public void setAttribute(String a, String b ) {
		String[] arr = { a , b };
		setAttribute( arr );
	}
	
	public void setAttribute(String a, String b, String c) {
		String[] arr = { a, b, c };
		setAttribute( arr );
	}

    /**
     *   What if setAttribute() is invoked before startSprite ?
     *
     */

	public void setAttribute(String attribute_) {
	    if ( attribute_ == null ) return;

        attributes.add( attribute_ ); // Is a set so there will be no double add.
	    if( !started ) {  return;   }

        // Ensures that a family of this attribute will get created if it wern't there.
        Vector<Sprite> vecSpr = Environment.familyOfAttributes.get( attribute_);

        // Add this sprite if it wasn't already there in that family.
        if( !vecSpr.contains(this) ) {
             vecSpr.add( this );
        }
	}

	public void run() {
	// Default implementation removes the sprite if its is dead.
		if( state == DYING || state == DEAD ) {
	        stopSprite();
		}
	}

   	/** sets the current state of the sprite
		If the newState is DEAD, then future calls to threadIsRunning()
		will return false.
	*/
	public synchronized void setState(int newState) {
			state = newState;
			firstTime = true;
	}
	/**
		This method is equivalent to  <br>
		startThread(NORMAL);
	*/
	protected void startSprite() {
		startSprite(NORMAL);
	}

	// Returns the name of the class
	public String getShortName() {
		String s = getClass().getName();
		return s.substring(s.lastIndexOf(".")+1);
	}
	public String getName() {
	    return getClass().getName();
	}

	/**
		This method does the following.
		(i) set the state of the calling object to "state"
		(ii) add the calling object to its family.
		(ii) start the calling object's thread.|| add to passive sprites

		Any one of the overloaded startThread() methods must
		be called in the last line of the constructor
		of all classes derived from Sprites.
	*/
	protected void startSprite(int state) {

		// Add the object to allSprites"
		// NOTE: allSprite must be sorted as per z order.

		int pos = Collections.binarySearch( Environment.allSprites,this);
		if( pos < 0 ) pos = - (pos+1);
		Environment.allSprites.insertElementAt(this,pos);

   		setAttribute( getClass().getName() ); // doesn't update familyOf Attributes as started = false;

  		// Lets update familyOfAttributes ourselves.
        // This creates an attribute with sprite's class name if it weren't created already.
        for( String attr : attributes ) {
           Environment.getAttribute( attr ).add( this );
	    }

		this.state = state;
		started = true;

		if( (Game.verbose & Game.VERBOSE_LIFECYCLE) != 0 ) {
			Game.println( " [" + getShortName() + " born]");
        }

	}

	/**
	 * This is a way of instantly killing an sprite. A sprite is dead
	 * at the end of this call. Typically this would be used by rooms
	 * to kill all sprites or a subset of sprites instantly.
	*/
	protected void stopSprite() {
		state = DEAD;		// Don't meddle with me anymore. Safe Code.
		//removeFromFamily();	// remove from Environment.familyOfFamilies & allSprites
		Environment.allSprites.removeElement(this);
		//Vector<Sprite> v = Environment.getFamily( getClass().getName() );
		//v.removeElement(this);

		unsetAttribute();	// remove from Environment.familyOfAttributes if needed.

		if( (Game.verbose & Game.VERBOSE_LIFECYCLE) != 0 )
			Game.print( " [" + getShortName() + " dead]");
	}

	public static void update() {
       for( Enumeration e= Environment.allSprites.elements(); e.hasMoreElements(); ) {
           Sprite s = (Sprite) e.nextElement();
           s.run();
      }
	}

	public static void render( Graphics g, Viewport vp,int XXX,int YYY ) {
	    if( (Game.debugFlag != null)&& Game.debugFlag.equals("rect") ) {
               Sprite.render( g , vp,true,XXX,YYY);
         }
	     else {
                Sprite.render( g ,vp, false,XXX,YYY);
        }
	}
	// Move this method to a Util class during cleanup. // primarily for debugging.
	public static String polyToString(Polygon p) {
		String s = "Polygon =";
		for( int i=0;i<p.npoints; i++ ) {
			s = s + " ("+p.xpoints[i]+","+p.ypoints[i]+")";
		}
		return s;
	}
	
	public static String rectToString(Rectangle r) {
		String s = "Rect["+ r.x + "," + r.y + "," + r.width + "," + r.height + "]";
		return s;
		
	}
	
		public static void render( Graphics g, Viewport vp,boolean drawBoundingRectangle,int XXX,int YYY) {
		if( drawBoundingRectangle ) {

			g.setColor(Color.black);
			for( Enumeration e= Environment.allSprites.elements(); e.hasMoreElements(); ) { // draw allsprites to backbuffer
				Sprite s = (Sprite) e.nextElement();
				if( !s.visible ) continue;
				CImage c = s.res.img[s.type][s.curFrame];
				Rectangle r = c.getBounds();
				if( c.img == null )
					throw new GamePackException("images[curFrame]==null");
				s.draw(g,vp,XXX,YYY);				
//				System.out.println( "x = " + r.x + " y = " + r.y + " w = " + r.w;

				int w = c.img.getWidth(null);
				int h = c.img.getHeight(null);

				g.setColor(Color.red);
				//g.drawRect( XXX+ -vp.rect.x+ s.x+r.x, YYY+ -vp.rect.y+s.y+r.y, r.width-1,r.height-1); // orig
				Rectangle rng = new Rectangle( s.getBounds() );
				rng.translate( XXX+ -vp.rect.x , YYY+ -vp.rect.y );
				if (!s.hasSubShape() ) {
					g.drawRect( rng.x, rng.y, rng.width-1, rng.height-1 );
				}
					// Anywhere you use rect for collision testing you will be using rect.width-1 and rect.height-1 
					g.drawRect( XXX+ -vp.rect.x+s.x-1, YYY+ -vp.rect.y+s.y-1, 2,2);
				
				
				if ( s.hasSubShape() ) {
					//System.out.print("." );
					Polygon pol = s.getSubShape();
					Polygon p = new Polygon( pol.xpoints, pol.ypoints, pol.npoints );
					p.translate( XXX+ -vp.rect.x , YYY+ -vp.rect.y );
					//System.out.println( polyToString( p ));
					g.setColor(Color.red);
					g.drawPolygon( p );					
				}
			}
		} else {
          for( Enumeration e= Environment.allSprites.elements(); e.hasMoreElements(); ) { // draw allsprites to backbuffer
			Sprite s = (Sprite) e.nextElement();
			s.draw(g,vp,XXX,YYY);
			/*if( !s.visible ) continue;
			//CImage c = s.images[s.curFrame];
			CImage c = s.res.img[s.type][s.curFrame];
			if( c.img == null )
				throw new GamePackException("images[curFrame]==null");
			g.drawImage(c.img, -vp.rect.x+s.x+c.x, -vp.rect.y+s.y+c.y,null);*/
		  }
        }
	}
	public void draw(Graphics g,Viewport vp,int XXX,int YYY) {
        if( visible ) {
			CImage c = res.img[type][curFrame];
			if( c.img == null ) {
				throw new GamePackException("images[curFrame]==null");
			}
			// The sprite will be drawn at ax1,ay1 on the Window
			int ax1 = x+c.x;
			int ay1 = y+c.y;
            int ax2 = ax1 + c.img.getWidth(null);
            int ay2 = ay1 + c.img.getHeight(null);

            Rectangle r = vp.rect; // To be used for clipping.
            int bx1 = r.x;
            int by1 = r.y;
            int bx2 = bx1 + r.width;
            int by2 = by1 + r.height;

            boolean collided = ( ax2 < bx1 || ax1 > bx2 || ay2 < by1 || ay1 > by2) ?  false : true;
            //if( ax1 > bx2 ) return;

            if( !collided ) {  return; }
			else {
              // Is the sprite completely inside or needs to be clipped?
                 boolean topLeftInside = inside( bx1,by1,bx2,by2,  ax1,ay1)? true : false;
                 boolean bottomRightInside = inside( bx1,by1,bx2,by2,  ax2,ay2)? true : false;

                 if( topLeftInside & bottomRightInside ) {
                     g.drawImage(c.img,XXX -vp.rect.x+ x+c.x,YYY -vp.rect.y+ y+c.y,null);
                 } else {
                 // there is intersection, the sprite needs to be clipped.
                 int offsetX = -vp.rect.x;
                 int offsetY = -vp.rect.y;
                 int mx=0,my=0;
                 int mw=c.img.getWidth(null);
                 int mh=c.img.getHeight(null);

                 if( ax1 >= bx1 && ax2 >= bx2 ) {
                      mw = mw - (ax2-bx2);
                     // if( c.x == 0 ) { System.out.println("#1 ax1 = " + ax1 + " > bx1 = " + bx1 + " mw = " + mw);                 }
                 }
                 else if( ax1 < bx1 && ax2 > bx2 ) {
                      mx = bx1 - ax1;
                      mw = vp.rect.width;
                      ax1 = -offsetX; // so that offsetX + ax1 = 0;
                      // if( c.x == 0 ) { System.out.println("#2 ax1(" + ax1 + ") bx1(" + bx1 + ") ax2(" + ax2 + ") bx2(" + bx2 + ") mw(" + mw + ")");                 }
                 } else if ( ax1 < bx1 && ax2<= bx2) {
                     mx = bx1-ax1;
                     mw = mw - mx;
                     ax1 = -offsetX;
                 }

                 if( ay1 >= by1 && ay2 >= by2 ) {
                      mh = mh - (ay2-by2);
                     // if( c.x == 0 ) { System.out.println("#1 ay1 = " + ay1 + " > bx1 = " + bx1 + " mh = " + mh);                 }
                 }
                 else if( ay1 < by1 && ay2 > by2 ) {
                      my = by1 - ay1;
                      mh = vp.rect.height;
                      ay1 = -offsetY; // so that offsetX + ay1 = 0;
                      // if( c.x == 0 ) { System.out.println("#2 ay1(" + ay1 + ") bx1(" + bx1 + ") ay2(" + ay2 + ") bx2(" + bx2 + ") mh(" + mh + ")");                 }
                 } else if ( ay1 < by1 && ay2<= by2) {
                     my = by1-ay1;
                     mh = mh - my;
                     ay1 = -offsetY;
                 }

                 g.drawImage(c.img,XXX+ offsetX+ax1 ,YYY+ offsetY+ay1,XXX+ offsetX+ax1+mw, YYY+ offsetY+ay1+mh,
                                    mx,my,mx+mw,my+mh,
                             null);
              }

			}
        }
	}
	// -------------------------------------
	

	private static boolean inside(int ax1,int ay1,int ax2,int ay2, int px, int py) {
	        return ( px >= ax1 && px <= ax2 && py >= ay1 && py <= ay2 ) ? true : false;
	}

	private Set<String> attributes = new HashSet<String>();

}